﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RuleOfReady.Core.DomainModels.Tiles;

namespace RuleOfReady.Core.DomainModels.Sets
{
    /// <summary>
    /// Interface for a possible set of Mahjong Tiles
    /// </summary>
    public abstract class MahjongPossibleSet : IEnumerable<MahjongTile>, IEquatable<MahjongPossibleSet>
    {
        #region Public Properties (protected write)

        /// <summary>
        /// The Mahjong Tiles that make up this potential set
        /// </summary>
        public abstract IEnumerable<MahjongTile> Tiles { get; protected set; }

        /// <summary>
        /// A list of tiles that can be used to make a set with the partial set. If this is empty, then this potential set is a complete set.
        /// </summary>
        public abstract IEnumerable<MahjongTile> WaitingTiles { get; protected set; }

        /// <summary>
        /// Flag: True if this set is complete.
        /// </summary>
        public abstract bool CompletedSet { get; protected set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Add a tile to the set. This returns null if the combined tiles do not form a set.
        /// </summary>
        /// <param name="addTile">tile to add</param>
        /// <returns>the new set, or null if the tiles do not form a set</returns>
        public abstract MahjongPossibleSet Add(MahjongTile addTile);

        #region IEnumerable<MahjongTile> implementation

        /// <summary>
        /// Return the strongly typed enumerator for this collection
        /// </summary>
        /// <returns></returns>
        public IEnumerator<MahjongTile> GetEnumerator()
        {
            return this.Tiles.GetEnumerator();
        }

        /// <summary>
        /// Return the weakly types enumerator for this collection
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.Tiles.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// ToString Override: string representation of the set
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Join(" ", this.Tiles.Select(tile => tile.ToString()));
        }

        #endregion

        public bool Equals(MahjongPossibleSet other)
        {
            if (Object.ReferenceEquals(other, null))
                return false;
            if (Object.ReferenceEquals(other, this))
                return true;

            IEnumerable<MahjongTile> sortedThis = this.OrderBy(tile => tile);
            IEnumerable<MahjongTile> sortedOther = other.OrderBy(tile => tile);
            return Enumerable.SequenceEqual(sortedThis, sortedOther);
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as MahjongPossibleSet);
        }

        public override int GetHashCode()
        {
            int hashCode = 11;
            IEnumerable<MahjongTile> sortedThis = this.OrderBy(tile => tile);
            foreach (MahjongTile tile in sortedThis)
                hashCode ^= tile.GetHashCode() << 1;
            return hashCode;
        }


        /// <summary>
        /// Definition of '==' for Mahjong Possible Set. Handles null values first.
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator ==(MahjongPossibleSet left, MahjongPossibleSet right)
        {
            bool leftIsNull = Object.ReferenceEquals(left, null);
            bool rightIsNull = Object.ReferenceEquals(right, null);

            if (leftIsNull && rightIsNull)
                return true;
            else if (leftIsNull || rightIsNull)
                return false;
            else
                return left.Equals(right);
        }

        /// <summary>
        /// Definition of '!=' for Mahjong Possible Set
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns></returns>
        public static bool operator !=(MahjongPossibleSet left, MahjongPossibleSet right)
        {
            return !(left == right);
        }
    }
}
